/**********************************************************************
 *   Copyright: (C)2024 LingYun IoT System Studio
 *      Author: LiJiahui<2199250859@qq.com>
 *
 * Description: The purpose of this code is to provide a simple C library,
 *              which providing software bit-bang of the I2C protocol on
 *              any GPIO pins for ISKBoard.
 *
 *   ChangeLog:
 *        Version    Date       Author            Description
 *        V1.0.0  2025.07.19    LiJiahui      Release initial version
 *
 ***********************************************************************/

#include "hal_oled.h"
#include "stdio.h"

enum
{
	OLED_CMD = 0,
	OLED_DATA,
};


/* 向OLED发送命令 */
static void IIC_Write_Command(uint8_t command)
{
	I2C_StartCondition(OLED_I2CBUS);

	I2C_SendAddress(OLED_I2CBUS, I2C_WR);

	I2C_WriteByte(OLED_I2CBUS, 0x00);
	I2C_WriteByte(OLED_I2CBUS, command);

	I2C_StopCondition(OLED_I2CBUS);
}


/* 向OLED写入数据 */
static void IIC_Write_Data(uint8_t data)
{
	I2C_StartCondition(OLED_I2CBUS);

	I2C_SendAddress(OLED_I2CBUS, I2C_WR);

	I2C_WriteByte(OLED_I2CBUS, 0x40);
	I2C_WriteByte(OLED_I2CBUS, data);

	I2C_StopCondition(OLED_I2CBUS);
}


/* OLED write a byte command data or command */
void OLED_WR_Byte(uint8_t data, uint8_t type)
{
	if( OLED_CMD == type )
	{
		IIC_Write_Command(data);
	}
	else
	{
		IIC_Write_Data(data);
	}
}



/*
 *+-------------------------------------------------+
 *|        OLED initial/control function API        |
 *+-------------------------------------------------+
 */

/* 开启OLED显示 */
void OLED_Display_On(void)
{
	OLED_WR_Byte(0x8D, OLED_CMD); //发送电荷泵设置命令
	OLED_WR_Byte(0x14, OLED_CMD); //开启电荷泵
	OLED_WR_Byte(0xAF, OLED_CMD); //发送开启显示命令
}

/* turn OLED off */
void OLED_Display_off(void)
{
	OLED_WR_Byte(0x8D, OLED_CMD); //发送电荷泵设置命令
	OLED_WR_Byte(0x10, OLED_CMD); //关闭电荷泵
	OLED_WR_Byte(0xAE, OLED_CMD); //发送关闭显示命令
}


/* 清空OLED屏幕，清屏后，屏幕上所有像素点都熄灭 */
void OLED_Clear(void)
{
	uint8_t i, j;

	/* update display
	 * 遍历OLED的八个页，OLED屏幕在硬件上被分为八业，每页八行像素,128列宽，每列8个像素
	 */
	for( i=0; i<8; i++ )
	{
		OLED_WR_Byte(0xb0+i, OLED_CMD); //设置当前要操作的页地址，范围从0xb0-0xb7
		OLED_WR_Byte(0x00, OLED_CMD);	//设置列地址的低四位（从第0列开始）
		OLED_WR_Byte(0x10, OLED_CMD);	//设置列地址的高四位（从第0列开始）

		//遍历当前页的128列
		for( j=0; j<128; j++ )
		{
			OLED_WR_Byte(0, OLED_DATA);
		}
	}

	HAL_Delay(50);

	//清屏后，将光标重置到左上角（0，0）
	OLED_Set_Pos(0, 0);
}


/* 点亮整个OLED屏幕
 * 此处向每列的第一个像素点，若想全屏点亮，应该写入0FF
 */
void OLED_On(void)
{
	uint8_t i, j;

	//遍历所有页和列
	for( i=0; i<8; i++ )
	{
		OLED_WR_Byte(0xb0+i, OLED_CMD);
		OLED_WR_Byte(0x00, OLED_CMD);
		OLED_WR_Byte(0x10, OLED_CMD);

		for( j=0; j<128; j++ )
		{
			OLED_WR_Byte(1, OLED_DATA);
		}
	}
}


void OLED_Init(void)
{
	if(i2c_init(OLED_I2CBUS, OLED_CHIPADDR))
	{
		printf("OLED Init failure\r\n");
		return ;
	}

    //初始化命令，用于配置OLED的工作模式
	OLED_WR_Byte(0xAE,OLED_CMD); // 关闭显示

	OLED_WR_Byte(0x40,OLED_CMD); // 设置显示起始行 (Set Display Start Line)
	OLED_WR_Byte(0xB0,OLED_CMD); // 设置页起始地址 (Set Page Start Address)

	OLED_WR_Byte(0xC8,OLED_CMD); // 设置COM口扫描方向，C8是反向，即屏幕垂直翻转

	OLED_WR_Byte(0x81,OLED_CMD); // 设置对比度命令
	OLED_WR_Byte(0xff,OLED_CMD); // 设置对比度值 (0x00-0xff)，0xff为最大对比度

	OLED_WR_Byte(0xa1,OLED_CMD); // 设置段重映射，a1是反向，即屏幕水平镜像

	OLED_WR_Byte(0xa6,OLED_CMD); // 设置为正常显示模式 (A6:正常, A7:反相)

	OLED_WR_Byte(0xa8,OLED_CMD); // 设置多路复用率 (Set MUX Ratio)
	OLED_WR_Byte(0x1f,OLED_CMD); // 设置为32路复用 (适用于128x32屏幕, 128x64应为0x3f)

	OLED_WR_Byte(0xd3,OLED_CMD); // 设置显示偏移 (Set Display Offset)
	OLED_WR_Byte(0x00,OLED_CMD); // 不偏移

	OLED_WR_Byte(0xd5,OLED_CMD); // 设置时钟分频/振荡器频率
	OLED_WR_Byte(0xf0,OLED_CMD); // 设置分频值

	OLED_WR_Byte(0xd9,OLED_CMD); // 设置预充电周期 (Set Pre-charge Period)
	OLED_WR_Byte(0x22,OLED_CMD); // 设置预充电周期值

	OLED_WR_Byte(0xda,OLED_CMD); // 设置COM引脚硬件配置 (Set COM Pins Hardware Configuration)
	OLED_WR_Byte(0x02,OLED_CMD); // (适用于128x32屏幕, 128x64应为0x12)

	OLED_WR_Byte(0xdb,OLED_CMD); // 设置VCOMH取消选择电平
	OLED_WR_Byte(0x49,OLED_CMD); // 设置电平值

	OLED_WR_Byte(0x8d,OLED_CMD); // 电荷泵设置命令
	OLED_WR_Byte(0x14,OLED_CMD); // 开启电荷泵

	OLED_WR_Byte(0xaf,OLED_CMD); // 开启显示

    // 初始化完成后，清空一次屏幕
	OLED_Clear();
}



/*
 *+-------------------------------------------------+
 *|          OLED display function API              |
 *+-------------------------------------------------+
 */

/* 设置写入数据的起始坐标
 * x 列坐标（0-127）
 * y 页坐标（0-7）
 */
void OLED_Set_Pos(uint8_t x, uint8_t y)
{
	OLED_WR_Byte(0xb0+y, OLED_CMD); //设置页地址，范围0xb0-0xb7
	OLED_WR_Byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD); //设置列地址的高四位
	OLED_WR_Byte((x & 0x0f), OLED_CMD);	//设置列地址的低四位
}


/* 在指定位置显示一个ASCII字符（通过访问字体库）
 * x 列坐标		y 页坐标
 * char 要显示的字符		char_size  字体大小，字体库定义的为 6x8(宽6，高8) 或 8x16（宽（列）8，高（行）16）
 */
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t char_size)
{
	uint8_t c = 0, i = 0;

	//计算字符在字体库数组中的偏移量，由于字体库通常从空格字符' '开始，
	//c计算的是字符相对于空格的偏移量
	c = chr - ' ';

	//如果x坐标超出屏幕宽度，则换到下一行显示（这里的下一行指下两页）
	if( x > X_WIDTH-1 )
	{
		x = 0;
		y = y + 2;
	}

	//如果是16像素高的字体
	if( char_size == 16 )
	{
		//8×16的字帖需要占用上下两个页(16行)
		OLED_Set_Pos(x, y); //设置上半部分字符的显示位置

		//循环八次，写入上半部分的八个字节数据
		for( i=0; i<8; i++ )
			OLED_WR_Byte(F8X16[c*16 + i], OLED_DATA);

		OLED_Set_Pos(x, y+1); //设置下半部分字符的显示位置（下一页）

		//循环八次，写入下半部分的八个字节数据
		for( i=0; i<8; i++ )
			OLED_WR_Byte(F8X16[c*16 + i + 8], OLED_DATA);
	}
	else //8像素高字帖（6x8字库）
	{
		OLED_Set_Pos(x, y);
		for( i=0; i<6; i++ ) //循环六次，写入六个字节的数据
			OLED_WR_Byte(F6x8[c][i], OLED_DATA);
	}
}


/* 在指定位置显示一个字符串
 * x 起始列坐标		y 起始页坐标
 * chr 要显示的字符串指针		font_size 字体大小
 */
void OLED_ShowString(uint8_t x, uint8_t y, char *chr, uint8_t font_size)
{
	uint8_t j = 0;

	//循环遍历字符串中的每个字符，直到遇到字符串结束符'\0'
	while( chr[j] != '\0' )
	{
		//调用单个字符显示函数来显示当前字符
		OLED_ShowChar(x, y, chr[j], font_size);

		//更新x坐标，为下一个字符准备位置。这里假设字符宽度为8像素
		x += 8;

		//如果x坐标超出范围（120是为了留边距），则换行
		if( x > 120 )
		{
			x = 0;
			y += 2;	//换到下两页
		}

		j++;
	}
}


/* 在指定位置显示一个16×16的汉字
 * Hzk 指向汉字字模库的指针（一个二维数组）
 * x 起始列坐标	y 起始页坐标
 * no 要显示的汉字在字模库中的索引号
 */
void OLED_ShowChinese(const uint8_t (*Hzk)[32], uint8_t x, uint8_t y, uint8_t no)
{
	uint8_t t;

	//一个16x16的汉字需要32个字节来存储，同样分两页显示
	OLED_Set_Pos(x, y); //设置上半部分的显示位置
	for( t=0; t<16; t++ ) //写入上半部分的16字节数据
	{
		printf("Hzk[2*no][t] = 0x%02X\n", Hzk[no][t]);  // 调试打印字模的原始数据
		printf("Byte %d for upper half of char %d: 0x%02X\n", t, no, Hzk[no][t]); // 调试打印字模字节
		OLED_WR_Byte(Hzk[no][t], OLED_DATA);
	}

	OLED_Set_Pos(x, y+1); //设置下半部分的显示位置
	for( t=16; t<32; t++ ) //写入下半部分的16字节数据
	{
		printf("Hzk[2*no][t] = 0x%02X\n", Hzk[no][t]);  // 调试打印字模的原始数据
		printf("Byte %d for down half of char %d: 0x%02X\n", t, no, Hzk[no][t]); // 调试打印字模字节
		OLED_WR_Byte(Hzk[no][t], OLED_DATA);
	}
}


/* 在OLED上显示一幅位图图像（128x64）
 * x0   起始列坐标   y0   起始页坐标
 * x1   结束列坐标   y1   结束页坐标
 * BMP  指向图像数据数组的指针
 */
void OLED_DrawBMP(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t *BMP)
{
	uint32_t 	j = 0;
	uint8_t		x, y;

	if( y1 % 8 == 0 )
		y = y1/8;
	else
		y = y1/8+1;

	//按页遍历，从起始页y0到结束页y1
	for( y=y0; y<y1; y++ )
	{
		//设置当前页的写入起始地址
		OLED_Set_Pos(x0, y);

		//按列遍历，从起始列x0到结束列x1
		for( x=x0; x<x1; x++ )
		{
			OLED_WR_Byte(BMP[j++], OLED_DATA);
		}
	}
}

void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size)
{
    uint8_t t, temp;
    uint8_t enShow = 0; // 用于处理前导零的标志

    for (t = 0; t < len; t++) {
        // 计算从高位到低位每一位的权值（例如 1000, 100, 10, 1）
        uint32_t pow10 = 1;
        for (int i = 0; i < len - t - 1; i++) {
            pow10 *= 10;
        }

        // 提取当前位的数字
        temp = (num / pow10) % 10;

        // 处理前导零
        // 如果数字的实际位数少于指定长度，且当前位是0
        if (enShow == 0 && t < (len - 1)) {
            if (temp == 0) {
                // 如果是最高位且为0，则显示为空格或不显示，这里选择空格
                OLED_ShowChar(x + (size / 2) * t, y, ' ', size);
                continue;
            } else {
                enShow = 1; // 遇到非0数字，开始显示
            }
        }

        // 显示当前位的数字字符
        OLED_ShowChar(x + (size / 2) * t, y, temp + '0', size);
    }
}



